package com.michir.projects.jms.mocks.runners;

import java.io.Serializable;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.michir.projects.jms.mocks.ConnectionFactoryMocker;
import com.michir.projects.jms.mocks.ConnectionMocker;
import com.michir.projects.jms.mocks.MessageProducerMocker;
import com.michir.projects.jms.mocks.ObjectMessageMocker;
import com.michir.projects.jms.mocks.SessionMocker;

/**
 * A JMS Context ({@link ConnectionFactory}, {@link Connection}, {@link Queue} ...) initializer for unit testing.
 * Actually uses {@link Mockito} for Mocking JMS API.
 * 
 * @author michir
 */
public class JmsProducerTestRunner {

	/**
	 * Mocked Queue.
	 */
	private Queue queue;
	
	/**
	 * Mocked Connection Factory
	 */
	private ConnectionFactory connectionFactory;

	/**
	 * Mocked connection.
	 */
	private Connection connection;
	
	/**
	 * Mocked JMS Producer.
	 */
	private MessageProducer producer;

	/**
	 * Mocked Object message.
	 */
	private ObjectMessage message;
	
	/**
	 * Build a new {@link JmsProducerTestRunner} instance.
	 * @param transacted whether the created session is transacted or not,, @see {@link Connection#createSession(boolean, int)}
	 * @param sessionMode Session creation mode, @see {@link Connection#createSession(boolean, int)}
	 * @param serializable the input object instance to put in the {@link ObjectMessage}
	 * @return the newly created JMS Test Runner
	 * @throws Exception
	 */
	public static JmsProducerTestRunner build(Boolean transacted, Integer acknowledgeMode, Serializable serializable) throws Exception {
		// Mock message
		ObjectMessage message = ObjectMessageMocker.mock(serializable);
		// Mock destination Queue
		Queue queue = Mockito.mock(Queue.class);
		// Mock Producer
		MessageProducer producer = MessageProducerMocker.mock(message);
		// Mock JMS Session
		Session session = SessionMocker.mock(message, producer, queue, transacted, acknowledgeMode);
		// Mock Connection
		Connection connection = ConnectionMocker.mock(session, transacted, acknowledgeMode);
		// Mock Connection Factory
		ConnectionFactory connectionFactory = ConnectionFactoryMocker.mock(connection);
		// Built instance
		JmsProducerTestRunner runner = new JmsProducerTestRunner();
		runner.setConnectionFactory(connectionFactory);
		runner.setQueue(queue);
		runner.setProducer(producer);
		runner.setConnection(connection);
		runner.setMessage(message);
		//
		return runner;
	}

	/**
	 * @return the queue
	 */
	public Queue getQueue() {
		return queue;
	}

	/**
	 * @param queue the queue to set
	 */
	public void setQueue(Queue queue) {
		this.queue = queue;
	}

	/**
	 * @return the connectionFactory
	 */
	public ConnectionFactory getConnectionFactory() {
		return connectionFactory;
	}

	/**
	 * @param connectionFactory the connectionFactory to set
	 */
	public void setConnectionFactory(ConnectionFactory connectionFactory) {
		this.connectionFactory = connectionFactory;
	}

	/**
	 * @return the connection
	 */
	public Connection getConnection() {
		return connection;
	}

	/**
	 * @param connection the connection to set
	 */
	public void setConnection(Connection connection) {
		this.connection = connection;
	}

	/**
	 * @return the producer
	 */
	public MessageProducer getProducer() {
		return producer;
	}

	/**
	 * @param producer the producer to set
	 */
	public void setProducer(MessageProducer producer) {
		this.producer = producer;
	}

	/**
	 * @return the message
	 */
	public ObjectMessage getMessage() {
		return message;
	}

	/**
	 * @param message the message to set
	 */
	public void setMessage(ObjectMessage message) {
		this.message = message;
	}

	/**
	 * Binds input listener to runner instance to be notified on messages sending.
	 * @param listener listener to bind
	 * @return <code>this</code> instance for chaining bindings
	 * @throws JMSException 
	 */
	public JmsProducerTestRunner bind(final MessageListener listener) throws JMSException {
		Mockito.doAnswer(new Answer<Void>() {
			@Override
			public Void answer(InvocationOnMock invocation) throws Throwable {
				listener.onMessage(message);
				return null;
			}
		}).when(producer).send(message);
		return this;
	}
}
